/*-
 * Copyright (c) 2010-2013 Kai Wang
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "ld.h"
#include "ld_arch.h"
#include "ld_file.h"
#include "ld_path.h"

ELFTC_VCSID("$Id: ld_file.c 2930 2013-03-17 22:54:26Z kaiwang27 $");

/*
 * Support routines for input file handling.
 */

static void _add_file(struct ld *ld, const char *name, enum ld_file_type type,
    int first, struct ld_file *after);

void
ld_file_cleanup(struct ld *ld)
{
	struct ld_file *lf, *_lf;
	struct ld_archive_member *lam, *_lam;

	TAILQ_FOREACH_SAFE(lf, &ld->ld_lflist, lf_next, _lf) {
		TAILQ_REMOVE(&ld->ld_lflist, lf, lf_next);
		free(lf->lf_name);
		if (lf->lf_ar != NULL) {
			HASH_ITER(hh, lf->lf_ar->la_m, lam, _lam) {
				HASH_DEL(lf->lf_ar->la_m, lam);
				free(lam->lam_name);
				free(lam);
			}
			free(lf->lf_ar);
		}
		free(lf);
	}
}

void
ld_file_add(struct ld *ld, const char *name, enum ld_file_type type)
{

	_add_file(ld, name, type, 0, NULL);
}

void
ld_file_add_first(struct ld *ld, const char *name, enum ld_file_type type)
{

	_add_file(ld, name, type, 1, NULL);
}

void
ld_file_add_after(struct ld *ld, const char *name, enum ld_file_type type,
    struct ld_file *after)
{

	_add_file(ld, name, type, 0, after);
}

void
ld_file_load(struct ld *ld, struct ld_file *lf)
{
	struct ld_archive *la;
	struct ld_state *ls;
	struct stat sb;
	Elf_Kind k;
	GElf_Ehdr ehdr;
	int fd;

	assert(lf != NULL && lf->lf_name != NULL);

	ls = &ld->ld_state;
	if (ls->ls_file == lf)
		return;

	if ((fd = open(lf->lf_name, O_RDONLY)) < 0)
		ld_fatal_std(ld, "%s: open", lf->lf_name);

	if (fstat(fd, &sb) < 0)
		ld_fatal_std(ld, "%s: stat", lf->lf_name);
	if (sb.st_size == 0)
		ld_fatal(ld, "%s: File truncated", lf->lf_name);

	lf->lf_size = sb.st_size;
	if ((lf->lf_mmap = mmap(NULL, lf->lf_size, PROT_READ, MAP_PRIVATE, fd,
	    (off_t) 0)) == MAP_FAILED)
		ld_fatal_std(ld, "%s: mmap", lf->lf_name);
	close(fd);

	if (lf->lf_type == LFT_BINARY)
		return;

	if ((lf->lf_elf = elf_memory(lf->lf_mmap, lf->lf_size)) == NULL)
		ld_fatal(ld, "%s: elf_memory failed: %s", lf->lf_name,
		    elf_errmsg(-1));

	k = elf_kind(lf->lf_elf);

	if (k == ELF_K_AR) {
		lf->lf_type = LFT_ARCHIVE;
		if (lf->lf_ar == NULL) {
			if ((la = calloc(1, sizeof(*la))) == NULL)
				ld_fatal_std(ld, "calloc");
			lf->lf_ar = la;
		}
		return;
	}

	assert(k != ELF_K_AR);
	if (k == ELF_K_NONE)
		ld_fatal(ld, "%s: File format not recognized", lf->lf_name);

	if (gelf_getehdr(lf->lf_elf, &ehdr) == NULL)
		ld_fatal(ld, "%s: gelf_getehdr failed: %s", lf->lf_name,
		    elf_errmsg(-1));

	switch (ehdr.e_type) {
	case ET_NONE:
		ld_fatal(ld, "%s: ELF type ET_NONE not supported", lf->lf_name);
	case ET_REL:
		lf->lf_type = LFT_RELOCATABLE;
		break;
	case ET_EXEC:
		ld_fatal(ld, "%s: ELF type ET_EXEC not supported yet",
		    lf->lf_name);
	case ET_DYN:
		lf->lf_type = LFT_DSO;
		break;
	case ET_CORE:
		ld_fatal(ld, "%s: ELF type ET_NONE not supported", lf->lf_name);
	default:
		ld_fatal(ld, "%s: unknown ELF type %u", ehdr.e_type);
	}

	ld_arch_verify(ld, lf->lf_name, ehdr.e_machine);
}

void
ld_file_unload(struct ld *ld, struct ld_file *lf)
{
	struct ld_state *ls;

	ls = &ld->ld_state;

	if (lf->lf_type != LFT_BINARY)
		elf_end(lf->lf_elf);

	if (lf->lf_mmap != NULL) {
		if (munmap(lf->lf_mmap, lf->lf_size) < 0)
			ld_fatal_std(ld, "%s: munmap", lf->lf_name);
	}

	if (ls->ls_file == lf)
		ls->ls_file = NULL;
}

static void
_add_file(struct ld *ld, const char *name, enum ld_file_type type,
    int first, struct ld_file *after)
{
	struct ld_state *ls;
	struct ld_file *lf;
	int fd;

	assert(ld != NULL && name != NULL);

	if (!strncmp(name, "-l", 2)) {
		ld_path_search_library(ld, &name[2]);
		return;
	}

	ls = &ld->ld_state;

	if ((lf = calloc(1, sizeof(*lf))) == NULL)
		ld_fatal_std(ld, "calloc");

	if ((lf->lf_name = strdup(name)) == NULL)
		ld_fatal_std(ld, "strdup");

	lf->lf_type = type;
	lf->lf_whole_archive = ls->ls_whole_archive;
	lf->lf_as_needed = ls->ls_as_needed;
	lf->lf_group_level = ls->ls_group_level;
	lf->lf_search_dir = ls->ls_search_dir;

	if ((fd = open(lf->lf_name, O_RDONLY)) < 0) {
		if (!lf->lf_search_dir)
			ld_fatal_std(ld, "%s: open", lf->lf_name);

		/* Search library path for this file. */
		ld_path_search_file(ld, lf);
	} else
		(void) close(fd);

	if (lf->lf_type == LFT_UNKNOWN && ls->ls_itgt != NULL) {
		if (elftc_bfd_target_flavor(ls->ls_itgt) == ETF_BINARY)
			lf->lf_type = LFT_BINARY;
	}

	if (lf->lf_type == LFT_DSO)
		ld->ld_dynamic_link = 1;

	if (after != NULL)
		TAILQ_INSERT_AFTER(&ld->ld_lflist, after, lf, lf_next);
	else if (first)
		TAILQ_INSERT_HEAD(&ld->ld_lflist, lf, lf_next);
	else
		TAILQ_INSERT_TAIL(&ld->ld_lflist, lf, lf_next);
}
